﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class AnimationData {
	public AnimationClip clip;
	public string clipName;
	public float speed = 1;
	public float transitionDuration = -1;
	public WrapMode wrapMode;
	[HideInInspector] public int timesPlayed = 0;
	[HideInInspector] public int stateHash;
}

[RequireComponent (typeof (Animator))]
public class MecanimControl : MonoBehaviour {

	public AnimationData defaultAnimation = new AnimationData();
	public AnimationData[] animations = new AnimationData[0];
	public bool debugMode = false;
	public bool alwaysPlay = false;
	public float defaultTransitionDuration = 0.15f;
	public WrapMode defaultWrapMode = WrapMode.Loop;

	private Animator animator;

	private int state1Hash;
	private int state2Hash;
	
	private RuntimeAnimatorController controller1;
	private RuntimeAnimatorController controller2;
	private RuntimeAnimatorController controller3;
	private RuntimeAnimatorController controller4;

	private AnimationData currentAnimationData;
	private bool currentMirror;
	private bool invertedSpeed;

	public delegate void AnimEvent(AnimationData animationData);
	public static event AnimEvent OnAnimationBegin;
	public static event AnimEvent OnAnimationEnd;
	public static event AnimEvent OnAnimationLoop;

	// Use this for initialization
	void Awake () {
		animator = gameObject.GetComponent<Animator>();
		state1Hash = Animator.StringToHash("State1");
		state2Hash = Animator.StringToHash("State2");
		controller1 = (RuntimeAnimatorController) Resources.Load("controller1");
		controller2 = (RuntimeAnimatorController) Resources.Load("controller2");
		controller3 = (RuntimeAnimatorController) Resources.Load("controller3");
		controller4 = (RuntimeAnimatorController) Resources.Load("controller4");
		if (defaultAnimation.clip == null && animations.Length > 0){
			SetDefaultClip(animations[0].clip, "Default", animations[0].speed, animations[0].wrapMode);
		}
		foreach(AnimationData animData in animations) animData.clip.wrapMode = animData.wrapMode;
	}

	void FixedUpdate(){
		//WrapMode emulator
		AnimationInfo[] animationInfoArray = animator.GetCurrentAnimationClipState(0);
		foreach (AnimationInfo animationInfo in animationInfoArray){
			if (animationInfo.weight < 1) continue;
			if (GetCurrentClipPosition() >= 1){
				if (animationInfo.clip.wrapMode == WrapMode.Loop || animationInfo.clip.wrapMode == WrapMode.PingPong) {
					if (MecanimControl.OnAnimationLoop != null) MecanimControl.OnAnimationLoop(currentAnimationData);
					currentAnimationData.timesPlayed ++;
				}else{
					if (currentAnimationData.timesPlayed == 0 && MecanimControl.OnAnimationEnd != null)
						MecanimControl.OnAnimationEnd(currentAnimationData);

					currentAnimationData.timesPlayed = 1;
				}

				if (animationInfo.clip.wrapMode == WrapMode.Default) animationInfo.clip.wrapMode = defaultWrapMode;

				if (animationInfo.clip.wrapMode == WrapMode.Loop) {
					SetCurrentClipPosition(0);
				}

				if (animationInfo.clip.wrapMode == WrapMode.Once ||
				    animationInfo.clip.wrapMode == WrapMode.ClampForever ||
				    animationInfo.clip.wrapMode == WrapMode.Clamp) {
					SetCurrentClipPosition(1);
				}
				
				if (animationInfo.clip.wrapMode == WrapMode.Once && alwaysPlay) Play(defaultAnimation, currentMirror);

				if (animationInfo.clip.wrapMode == WrapMode.PingPong) {
					SetSpeed(currentAnimationData.clipName, -currentAnimationData.speed);
					SetCurrentClipPosition(0);
				}
			}
		}
	}
	
	void OnGUI(){
		//Toggle debug mode to see the live data in action
		if (debugMode) {
			GUI.Box (new Rect (Screen.width - 400,0,400,175), "Animation Data");
			GUI.BeginGroup(new Rect (Screen.width - 390,20,400,165));{
				GUILayout.Label("Current Clip Name: "+ currentAnimationData.clipName);

				AnimationInfo[] animationInfoArray = animator.GetCurrentAnimationClipState(0);
				foreach (AnimationInfo animationInfo in animationInfoArray){
					AnimatorStateInfo animatorStateInfo = animator.GetCurrentAnimatorStateInfo(0);
					GUILayout.Label("Wrap Mode: "+ animationInfo.clip.wrapMode);
					GUILayout.Label("Normalized Time: "+ animatorStateInfo.normalizedTime);
					GUILayout.Label("Blend Weight: "+ animator.GetCurrentAnimationClipState(0)[0].weight);
				}

				GUILayout.Label("Current Speed: "+ GetSpeed().ToString());
				GUILayout.Label("Times Played: "+ currentAnimationData.timesPlayed);
			}GUI.EndGroup();
		}
	}

	public void RemoveClip(string name) {
		List<AnimationData> animationDataList = new List<AnimationData>(animations);
		animationDataList.Remove(GetAnimationData(name));
		animations = animationDataList.ToArray();
	}

	public void RemoveClip(AnimationClip clip) {
		List<AnimationData> animationDataList = new List<AnimationData>(animations);
		animationDataList.Remove(GetAnimationData(clip));
		animations = animationDataList.ToArray();
	}
	
	public void SetDefaultClip(AnimationClip clip, string name, float speed, WrapMode wrapMode) {
		defaultAnimation.clip = (AnimationClip) Instantiate(clip);
		defaultAnimation.clip.wrapMode = wrapMode;
		defaultAnimation.clipName = name;
		defaultAnimation.speed = speed;
		defaultAnimation.wrapMode = wrapMode;
		if (currentAnimationData == null) {
			currentAnimationData = defaultAnimation;
			Play(defaultAnimation.clip, defaultTransitionDuration, 0, currentMirror);
		}
	}
	
	public void AddClip(AnimationClip clip, string newName) {
		AddClip(clip, newName, 1, defaultWrapMode);
	}

	public void AddClip(AnimationClip clip, string newName, float speed, WrapMode wrapMode) {
		if (GetAnimationData(newName) != null) Debug.LogWarning("An animation with the name '"+ newName +"' already exists.");
		AnimationData animData = new AnimationData();
		animData.clip = (AnimationClip) Instantiate(clip);
		animData.clip.wrapMode = wrapMode;
		animData.clipName = newName;
		animData.speed = speed;
		animData.wrapMode = wrapMode;

		List<AnimationData> animationDataList = new List<AnimationData>(animations);
		animationDataList.Add(animData);
		animations = animationDataList.ToArray();
	}

	public AnimationData GetAnimationData(string clipName){
		foreach(AnimationData animData in animations){
			if (animData.clipName == clipName){
				return animData;
			}
		}
		if (clipName == defaultAnimation.clipName) return defaultAnimation;
		return null;
	}

	public AnimationData GetAnimationData(AnimationClip clip){
		foreach(AnimationData animData in animations){
			if (animData.clip == clip){
				return animData;
			}
		}
		if (clip == defaultAnimation.clip) return defaultAnimation;
		return null;
	}
	
	public void CrossFade(string clipName, float blendingTime){
		CrossFade(clipName, blendingTime, 0, currentMirror);
	}

	public void CrossFade(string clipName, float blendingTime, float normalizedTime, bool mirror){
		_playAnimation(GetAnimationData(clipName).clip, blendingTime, normalizedTime, mirror);
	}

	public void Play(string clipName, float blendingTime, float normalizedTime, bool mirror){
		_playAnimation(GetAnimationData(clipName).clip, blendingTime, normalizedTime, mirror);
	}
	
	public void Play(AnimationClip clip, float blendingTime, float normalizedTime, bool mirror){
		_playAnimation(clip, blendingTime, normalizedTime, mirror);
	}

	public void Play(string clipName, bool mirror){
		_playAnimation(GetAnimationData(clipName).clip, 0, 0, mirror);
	}

	public void Play(string clipName){
		_playAnimation(GetAnimationData(clipName).clip, 0, 0, currentMirror);
	}
	
	public void Play(AnimationClip clip, bool mirror){
		_playAnimation(clip, 0, 0, mirror);
	}

	public void Play(AnimationClip clip){
		_playAnimation(clip, 0, 0, currentMirror);
	}

	public void Play(AnimationData animationData, bool mirror){
		_playAnimation(animationData.clip, animationData.transitionDuration, 0, mirror);
	}

	public void Play(AnimationData animationData){
		_playAnimation(animationData.clip, animationData.transitionDuration, 0, currentMirror);
	}

	public void Play(){
		animator.speed = currentAnimationData.speed;
	}


	private void _playAnimation(AnimationClip clip, float blendingTime, float normalizedTime, bool mirror){
		//The overrite machine. Basically creates an overrideController, replace its core animations and restate it back in
		if (currentAnimationData == null || currentAnimationData.clip == null) return;
		if (GetAnimationData(clip) == null) return;

		float lengthVariation = currentAnimationData.clip.length;
		AnimatorOverrideController overrideController = new AnimatorOverrideController();
		AnimationData targetAnimationData = GetAnimationData(clip);
		currentMirror = mirror;

		invertedSpeed = false;
		if (mirror){
			if (targetAnimationData.speed > 0){
				overrideController.runtimeAnimatorController = controller2;
			}else{
				overrideController.runtimeAnimatorController = controller4;
				invertedSpeed = true;
			}
		}else{
			if (targetAnimationData.speed > 0){
				overrideController.runtimeAnimatorController = controller1;
			}else{
				overrideController.runtimeAnimatorController = controller3;
				invertedSpeed = true;
			}
		}
		overrideController["State1"] = currentAnimationData.clip;
		overrideController["State2"] = currentAnimationData.clip;
		overrideController["State3"] = currentAnimationData.clip;
		
		currentAnimationData.timesPlayed = 0;
		currentAnimationData = targetAnimationData;

		if (currentAnimationData.stateHash == state1Hash){
			overrideController["State2"] = clip;
			currentAnimationData.stateHash = state2Hash;
		}else{
			overrideController["State1"] = clip;
			currentAnimationData.stateHash = state1Hash;
		}

		if (blendingTime == -1) blendingTime = currentAnimationData.transitionDuration;
		if (blendingTime == -1) blendingTime = defaultTransitionDuration;

		if (blendingTime == 0 || blendingTime == -1){
			overrideController["State1"] = clip;
			overrideController["State2"] = clip;
			overrideController["State3"] = clip;
			animator.runtimeAnimatorController = overrideController;
			SetCurrentClipPosition(normalizedTime);
		}else{
			animator.runtimeAnimatorController = overrideController;
			animator.CrossFade(currentAnimationData.stateHash, (blendingTime/lengthVariation) * currentAnimationData.speed, 0, normalizedTime);
		}
		
		SetSpeed(currentAnimationData.speed);
		if (MecanimControl.OnAnimationBegin != null) MecanimControl.OnAnimationBegin(currentAnimationData);
	}
	
	public bool IsPlaying(string clipName){
		return IsPlaying(GetAnimationData(clipName));
	}

	public bool IsPlaying(AnimationClip clip){
		return IsPlaying(GetAnimationData(clip));
	}

	public bool IsPlaying(AnimationData animData){
		if (currentAnimationData == animData && animData.wrapMode == WrapMode.Once && animData.timesPlayed > 0) return false;
		if (currentAnimationData == animData) return true;
		
		AnimationInfo[] animationInfoArray = animator.GetCurrentAnimationClipState(0);
		foreach (AnimationInfo animationInfo in animationInfoArray){
			if (animData.clip == animationInfo.clip) return true;
		}
		return false;
	}
	
	public string GetCurrentClipName(){
		return currentAnimationData.clipName;
	}
	
	public AnimationData GetCurrentAnimationData(){
		return currentAnimationData;
	}
	
	public int GetCurrentClipPlayCount(){
		return currentAnimationData.timesPlayed;
	}

	public void SetCurrentClipPosition(float normalizedTime){
		SetCurrentClipPosition(normalizedTime, false);
	}

	public void SetCurrentClipPosition(float normalizedTime, bool pause){
		AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);
		animator.Play(info.nameHash, 0, normalizedTime);
		if (pause) Pause();
	}

	public float GetCurrentClipPosition(){
		AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);
		return info.normalizedTime;
	}
	
	public void Stop(){
		Play(defaultAnimation.clip, defaultTransitionDuration, 0, currentMirror);
	}
	
	public void Pause(){
		animator.speed = 0;
	}

	public void SetSpeed(AnimationClip clip, float speed){
		AnimationData animData = GetAnimationData(clip);
		animData.speed = speed;
		if (IsPlaying(clip)) SetSpeed(speed);
	}

	public void SetSpeed(string clipName, float speed){
		AnimationData animData = GetAnimationData(clipName);
		animData.speed = speed;
		if (IsPlaying(clipName)) SetSpeed(speed);
	}
	
	public void SetWrapMode(WrapMode wrapMode){
		defaultWrapMode = wrapMode;
	}
	
	public void SetWrapMode(AnimationData animationData, WrapMode wrapMode){
		animationData.wrapMode = wrapMode;
		animationData.clip.wrapMode = wrapMode;
	}

	public void SetWrapMode(AnimationClip clip, WrapMode wrapMode){
		AnimationData animData = GetAnimationData(clip);
		animData.wrapMode = wrapMode;
		animData.clip.wrapMode = wrapMode;
	}

	public void SetWrapMode(string clipName, WrapMode wrapMode){
		AnimationData animData = GetAnimationData(clipName);
		animData.wrapMode = wrapMode;
		animData.clip.wrapMode = wrapMode;
	}
	
	public void SetSpeed(float speed){
		if ((!invertedSpeed && speed < 0) ||
		    (invertedSpeed && speed > 0)){
			_playAnimation(currentAnimationData.clip, 0, animator.GetCurrentAnimatorStateInfo(0).normalizedTime, currentMirror);
		}
		animator.speed = Mathf.Abs(speed);
	}
	
	public void RestoreSpeed(){
		SetSpeed(currentAnimationData.speed);
	}
	
	public void Rewind(){
		SetSpeed(-currentAnimationData.speed);
	}

	public float GetSpeed(AnimationClip clip){
		AnimationData animData = GetAnimationData(clip);
		return animData.speed;
	}

	public float GetSpeed(string clipName){
		AnimationData animData = GetAnimationData(clipName);
		return animData.speed;
	}

	public float GetSpeed(){
		return animator.speed;
	}

	public void SetMirror(bool toggle){
		SetMirror(toggle, 0);
	}

	public void SetMirror(bool toggle, float blendingTime){
		if (currentMirror == toggle) return;
		if (blendingTime == 0) blendingTime = defaultTransitionDuration;
		_playAnimation(currentAnimationData.clip, blendingTime, animator.GetCurrentAnimatorStateInfo(0).normalizedTime, toggle);
	}

	public bool GetMirror(){
		return currentMirror;
	}
}
